package cn.ric.mycurd.plugin;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import org.apache.ibatis.jdbc.SQL;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import cn.ric.mycurd.annotations.ColumnName;
import cn.ric.mycurd.annotations.TableName;

/**
 * @author ric.zhang
 * @date 2021/1/18
 */
abstract class BaseSqlBuilder {
    final Map<String, String> insertValues = new HashMap<>(5);
    final Map<String, String> updateValues = new HashMap<>(3);

    /**
     * 防止被外部实例化
     */
    BaseSqlBuilder() {
    }

    /**
     * 初始化实例
     *
     * @param provider 目前仅支持 mysql
     * @return SqlBuilder 实例
     */
    static BaseSqlBuilder getInstance(final String provider) {
        final String mysql = "mysql";
        if (mysql.equalsIgnoreCase(provider)) {
            return new MysqlBuilder();
        } else {
            throw new IllegalArgumentException("Not implemented!");
        }
    }

    /**
     * 获取数据库中的列名
     *
     * @param clas                    PO 实体类
     * @param javaFieldNameIgnoreCase java 字段名，忽略大小写的
     * @return 大小写正确的 java 字段名
     */
    static String getColumnName(final Class<?> clas, final String javaFieldNameIgnoreCase) {
        final Optional<Field> first = Arrays.stream(clas.getDeclaredFields())
                .filter(field -> field.getName().equalsIgnoreCase(javaFieldNameIgnoreCase)).findFirst();
        Assert.isTrue(first.isPresent(), "field not found: " + javaFieldNameIgnoreCase);
        final Field field = first.get();
        final ColumnName meter = field.getAnnotation(ColumnName.class);
        return meter == null ? field.getName().replaceAll("([A-Z]+)", "_$1").toLowerCase() : meter.value();
    }

    /**
     * 获取 PO 实体类的类名
     *
     * @param clas                    PO 实体类
     * @param javaFieldNameIgnoreCase java 字段名，忽略大小写的
     * @return 大写正确的 java 字段名
     */
    static String getJavaFieldName(final Class<?> clas, final String javaFieldNameIgnoreCase) {
        final Optional<Field> first = Arrays.stream(clas.getDeclaredFields())
                .filter(field -> field.getName().equalsIgnoreCase(javaFieldNameIgnoreCase)).findFirst();
        Assert.isTrue(first.isPresent(), "field not found: " + javaFieldNameIgnoreCase);
        return first.get().getName();
    }

    /**
     * 获取数据库表名
     *
     * @param clas PO 实体类
     * @return 数据库表名
     */
    static String getTableName(final Class<?> clas) {
        final TableName meter = clas.getAnnotation(TableName.class);
        return meter == null ? clas.getSimpleName().replaceAll("(?i)PO$", "").replaceAll("(?!^)([A-Z]+)", "_$1").toLowerCase()
                : meter.value();
    }

    String buildCreateSql(final Class<?> poClass, final String[] keyProperties) {
        SQL sqlBuilder = new SQL();
        final String tableName = getTableName(poClass);
        sqlBuilder.INSERT_INTO('`' + tableName + '`');
        ReflectionUtils.doWithFields(poClass, (field) -> {
            // 忽略静态属性
            if (Modifier.isStatic(field.getModifiers())) {
                return;
            }
            final String javaFieldName = field.getName();
            String dbColumnName = getColumnName(poClass, javaFieldName);
            if (StringUtils.isEmpty(dbColumnName)) {
                return;
            }
            if (keyProperties != null) {
                for (int i = 0; i < keyProperties.length; i++) {
                    String keyProperty = keyProperties[i];
                    if (dbColumnName.equalsIgnoreCase(keyProperty)) {
                        keyProperties[i] = javaFieldName;
                        return;
                    }
                }
            }
            if (insertValues.containsKey(javaFieldName)) {
                sqlBuilder.VALUES('`' + dbColumnName + '`', insertValues.get(javaFieldName));
            } else {
                sqlBuilder.VALUES('`' + dbColumnName + '`', "#{" + javaFieldName + '}');
            }
        });
        return sqlBuilder.toString();
    }

    String buildUpdateAllFieldSql(final Class<?> poClass, final String[] whereJavaFieldNames) {
        SQL sqlBuilder = new SQL();
        final String tableName = getTableName(poClass);
        sqlBuilder.UPDATE('`' + tableName + '`');
        ReflectionUtils.doWithFields(poClass, (field) -> {
            // 忽略静态属性
            if (Modifier.isStatic(field.getModifiers())) {
                return;
            }
            final String fieldName = field.getName();
            String columnName = getColumnName(poClass, fieldName);
            if (StringUtils.isEmpty(columnName)) {
                return;
            }
            if (Arrays.stream(whereJavaFieldNames).anyMatch(fieldName::equalsIgnoreCase)) {
                sqlBuilder.WHERE(String.format("`%s` = #{%s}", columnName, fieldName));
            } else {
                if (updateValues.containsKey(fieldName)) {
                    sqlBuilder.SET(String.format("`%s` = %s", columnName, updateValues.get(fieldName)));
                } else {
                    sqlBuilder.SET(String.format("`%s` = #{%s}", columnName, fieldName));
                }
            }
        });
        return sqlBuilder.toString();
    }

    String buildUpdateSomeFieldsByPo(Class<?> poClass, String[] whereJavaFieldNames, String[] updateJavaFieldNames) {
        SQL sqlBuilder = new SQL();
        final String tableName = getTableName(poClass);
        sqlBuilder.UPDATE('`' + tableName + '`');
        for (String fieldName : updateJavaFieldNames) {
            String columnName = getColumnName(poClass, fieldName);
            String javaFieldName = getJavaFieldName(poClass, fieldName);
            if (updateValues.containsKey(javaFieldName)) {
                sqlBuilder.SET(String.format("`%s` = %s", columnName, updateValues.get(javaFieldName)));
            } else {
                sqlBuilder.SET(String.format("`%s` = #{%s}", columnName, javaFieldName));
            }
        }
        for (String keyProperty : whereJavaFieldNames) {
            sqlBuilder.WHERE(String.format("`%s` = #{%s}", getColumnName(poClass, keyProperty),
                    getJavaFieldName(poClass, keyProperty)));
        }
        return sqlBuilder.toString();
    }

    String buildUpdateListedFieldsSql(Class<?> poClass, String[] whereJavaFieldNames, String[] updateJavaFieldNames) {
        SQL sqlBuilder = new SQL();
        final String tableName = getTableName(poClass);
        sqlBuilder.UPDATE('`' + tableName + '`');
        for (String fieldName : updateJavaFieldNames) {
            String columnName = getColumnName(poClass, fieldName);
            String javaFieldName = getJavaFieldName(poClass, fieldName);
            if (updateValues.containsKey(javaFieldName)) {
                sqlBuilder.SET(String.format("`%s` = %s", columnName, updateValues.get(javaFieldName)));
            } else {
                sqlBuilder.SET(String.format("`%s` = #{param1.%s}", columnName, javaFieldName));
            }
        }
        for (String keyProperty : whereJavaFieldNames) {
            sqlBuilder.WHERE(String.format("`%s` = #{param1.%s}", getColumnName(poClass, keyProperty),
                    getJavaFieldName(poClass, keyProperty)));
        }
        return sqlBuilder.toString();
    }

    String buildSelectAllFieldsSql(Class<?> poClass, String[] whereJavaFieldNames) {
        SQL sqlBuilder = new SQL();
        final String tableName = getTableName(poClass);
        sqlBuilder.FROM('`' + tableName + '`');
        ReflectionUtils.doWithFields(poClass, (field) -> {
            // 忽略静态属性
            if (Modifier.isStatic(field.getModifiers())) {
                return;
            }
            String columnName = getColumnName(poClass, field.getName());
            if (StringUtils.isEmpty(columnName)) {
                return;
            }
            sqlBuilder.SELECT(String.format("`%s` AS '%s'", columnName, field.getName()));
        });
        for (int i = 0; i < whereJavaFieldNames.length; i++) {
            String fieldName = whereJavaFieldNames[i];
            sqlBuilder.WHERE(String.format("`%s` = #{param%d}", getColumnName(poClass, fieldName), i + 1));
        }
        return sqlBuilder.toString();
    }

    String buildSelectSomeFieldsSql(Class<?> poClass, String[] whereJavaFieldNames, String[] selectJavaFieldNames) {
        SQL sqlBuilder = new SQL();
        final String tableName = getTableName(poClass);
        sqlBuilder.FROM('`' + tableName + '`');
        for (String fieldName : selectJavaFieldNames) {
            sqlBuilder.SELECT(
                    String.format("`%s` AS '%s'", getColumnName(poClass, fieldName), getJavaFieldName(poClass, fieldName)));
        }
        for (int i = 0; i < whereJavaFieldNames.length; i++) {
            sqlBuilder.WHERE(String.format("`%s` = #{param%d}", getColumnName(poClass, whereJavaFieldNames[i]), i + 1));
        }
        return sqlBuilder.toString();
    }

    /**
     * 构建获取总数据量的 sql
     *
     * @param poClass             PO 实体类
     * @param whereJavaFieldNames 查询条件，java 字段名，忽略大小写
     * @return 获取总数据量的 sql
     */
    String buildCountSql(Class<?> poClass, String[] whereJavaFieldNames) {
        SQL sqlBuilder = new SQL();
        sqlBuilder.SELECT("count(*)");
        final String tableName = getTableName(poClass);
        sqlBuilder.FROM('`' + tableName + '`');
        for (int i = 0; i < whereJavaFieldNames.length; i++) {
            sqlBuilder.WHERE(String.format("`%s` = #{param%d}", getColumnName(poClass, whereJavaFieldNames[i]), i + 1));
        }
        return sqlBuilder.toString();
    }

    /**
     * 构建查询全部列的 sql，sql 返回的是数据集，最后两个参数为 offset 和 length，用于分页查询
     *
     * @param poClass             PO 实体类
     * @param whereJavaFieldNames 查询条件，java 字段名，忽略大小写
     * @param orderJavaFieldNames 排序条件，java 字段名 + Desc| Asc，忽略大小写
     * @return 查询全部列的 sql
     */
    abstract String buildListAllFieldSql(Class<?> poClass, String[] whereJavaFieldNames, String[] orderJavaFieldNames);

    /**
     * 构建指定列的 sql，sql 返回的是数据集，最后两个参数为 offset 和 length，用于分页查询
     *
     * @param poClass              PO 实体类
     * @param whereJavaFieldNames  查询条件，java 字段名，忽略大小写
     * @param selectJavaFieldNames 将要查询的列，java 字段名，忽略大小写
     * @param orderJavaFieldNames  排序条件，java 字段名 + Desc| Asc，忽略大小写
     * @return 查询指定列的 sql
     */
    abstract String buildListSomeFieldsSql(Class<?> poClass, String[] whereJavaFieldNames,
                                           String[] selectJavaFieldNames, String[] orderJavaFieldNames);

    String buildDeleteSql(Class<?> poClass, String[] whereJavaFieldNames) {
        SQL sqlBuilder = new SQL();
        final String tableName = getTableName(poClass);
        sqlBuilder.DELETE_FROM('`' + tableName + '`');
        for (String fieldName : whereJavaFieldNames) {
            sqlBuilder.WHERE(
                    String.format("`%s` = #{%s}", getColumnName(poClass, fieldName), getJavaFieldName(poClass, fieldName)));
        }
        return sqlBuilder.toString();
    }
}
